---
title: Vue Examples
description: Examples for testing Vue components with Cypress.
sidebar_position: 30
sidebar_label: Examples
---

<ProductHeading product="app" />

# Vue Examples

:::info

##### <Icon name="question-circle" color="#4BBFD2" /> What you'll learn

- How to mount Vue components in Cypress
- How to pass props and events to components
- How to use slots in components
- How to use Vue Test Utils with Cypress
- How to customize `cy.mount()` with Vue

:::

## Mounting Components

### Using `cy.mount()`

To mount a component with `cy.mount()`, import the component and pass it to the
method:

```ts
import { Stepper } from './Stepper.vue'

it('mounts', () => {
  cy.mount(Stepper)
})
```

### Passing Data to a Component

You can pass props and events to a component by setting `props` in the options:

```js
cy.mount(Stepper, {
  props: {
    initial: 100,
  },
})
```

### Testing Event Handlers

Pass a Cypress [spy](/app/guides/stubs-spies-and-clocks#Spies) to an event
prop and validate it was called:

```js
it('clicking + fires a change event with the incremented value', () => {
  const onChangeSpy = cy.spy().as('onChangeSpy')
  cy.mount(Stepper, { props: { onChange: onChangeSpy } })
  cy.get('[data-cy=increment]').click()
  cy.get('@onChangeSpy').should('have.been.calledWith', 1)
})
```

### Using JSX

The mount command also supports JSX syntax (provided that you've configured your
bundler to support transpiling JSX or TSX files). Some might find using JSX
syntax beneficial when writing tests.

Sample with JSX:

```jsx
it('clicking + fires a change event with the incremented value', () => {
  const onChangeSpy = cy.spy().as('onChangeSpy')
  cy.mount(<Stepper initial={100} onChange={onChangeSpy} />)
  cy.get('[data-cy=increment]').click()
  cy.get('@onChangeSpy').should('have.been.calledWith', 101)
})
```

## Using Slots

### Default Slot

<Tabs>
<TabItem value="DefaultSlot.cy.js">

```js
import DefaultSlot from './DefaultSlot.vue'

describe('<DefaultSlot />', () => {
  it('renders', () => {
    cy.mount(DefaultSlot, {
      slots: {
        default: 'Hello there!',
      },
    })
    cy.get('div.content').should('have.text', 'Hello there!')
  })
})
```

</TabItem>
<TabItem value="DefaultSlot.cy.jsx (JSX)">

```jsx
import DefaultSlot from './DefaultSlot.vue'

describe('<DefaultSlot />', () => {
  it('renders', () => {
    cy.mount(<DefaultSlot>Hello there!</DefaultSlot>)
    cy.get('div.content').should('have.text', 'Hello there!')
  })
})
```

</TabItem>
<TabItem value="DefaultSlot.vue">

```html
<template>
  <div>
    <div class="content">
      <slot />
    </div>
  </div>
</template>

<script setup></script>
```

</TabItem>
</Tabs>

### Named Slot

<Tabs>
<TabItem value="NamedSlot.cy.js">

```js
import NamedSlot from './NamedSlot.vue'

describe('<NamedSlot />', () => {
  it('renders', () => {
    const slots = {
      header: 'my header',
      footer: 'my footer',
    }
    cy.mount(NamedSlot, {
      slots,
    })
    cy.get('header').should('have.text', 'my header')
    cy.get('footer').should('have.text', 'my footer')
  })
})
```

</TabItem>
<TabItem value="NamedSlot.cy.jsx (JSX)">

```jsx
import NamedSlot from './NamedSlot.vue'

describe('<NamedSlot />', () => {
  it('renders', () => {
    const slots = {
      header: 'my header',
      footer: 'my footer',
    }
    cy.mount(<NamedSlot>{{ ...slots }}</NamedSlot>)
    cy.get('header').should('have.text', 'my header')
    cy.get('footer').should('have.text', 'my footer')
  })
})
```

</TabItem>
<TabItem value="NamedSlot.vue">

```html
<template>
  <div>
    <header>
      <slot name="header" />
    </header>
    <footer>
      <slot name="footer" />
    </footer>
  </div>
</template>

<script setup></script>
```

</TabItem>
</Tabs>

For more info on testing Vue components with slots, refer to the
[Vue Test Utils Slots guide](https://test-utils.vuejs.org/guide/advanced/slots.html).

## Using Vue Test Utils

In order to encourage interoperability between your existing component tests and
Cypress, we support using Vue Test Utils' API.

```js
cy.mount(Stepper).then(({ wrapper, component }) => {
  // `wrapper` is the Vue Test Utils wrapper
  // `component` is the component instance itself
})
```

{/* TODO: Custom mount command docs */}

If you intend to use the `wrapper` frequently and use Vue Test Util's API, we
recommend you write a [custom mount command](/api/commands/mount) and create a
Cypress alias to get back at the `wrapper`.

```js
import { mount } from 'cypress/vue'

Cypress.Commands.add('mount', (...args) => {
  return mount(...args).then(({ wrapper }) => {
    return cy.wrap(wrapper).as('vue')
  })
})

// the "@vue" alias will now work anywhere
// after you've mounted your component
cy.mount(Stepper).doStuff().get('@vue') // The subject is now the Vue Wrapper
```

This means that you are able to get to the resulting `wrapper` returned from the
`mount` command and use `wrapper.emitted()` in order to gain access to Native
DOM events that were fired, as well as custom events that were emitted by your
component under test.

Because `wrapper.emitted()` is only data, and NOT spy-based you will have to
unpack its results to write assertions.

Your test failure messages will not be as helpful because you're not able to use
the Sinon-Chai library that Cypress ships, which comes with methods such as
`to.have.been.called` and `to.have.been.calledWith`.

Usage of the `cy.get('@vue')` alias may look something like the below code
snippet.

Notice that we're using the `'should'` function signature in order to take
advantage of Cypress's [retryability](/app/guides/test-retries). If we
chained using `cy.then` instead of `cy.should`, we may run into the kinds of
issues you have in Vue Test Utils tests where you have to use `await` frequently
in order to make sure the DOM has updated or any reactive events have fired.

<Tabs>
<TabItem value="With emitted">

```js
cy.mount(Stepper, { props: { initial: 100 } })
cy.get(incrementSelector).click()
cy.get('@vue').should(({ wrapper }) => {
  expect(wrapper.emitted('change')).to.have.length
  expect(wrapper.emitted('change')[0][0]).to.equal('101')
})
```

</TabItem>
<TabItem value="With spies">

```js
const onChangeSpy = cy.spy().as('onChangeSpy')

cy.mount(Stepper, { props: { initial: 100, onChange: onChangeSpy } })

cy.get(incrementSelector).click()
cy.get('@onChangeSpy').should('have.been.calledWith', '101')
```

</TabItem>
</Tabs>

Regardless of our recommendation to use spies instead of the internal Vue Test
Utils API, you may decide to continue using `emitted` as it _automatically_
records every single event emitted from the component, and so you won't have to
create a spy for every event emitted.

This auto-spying behavior could be useful for components that emit _many_ custom
events.

## Custom Mount Commands

### Customizing `cy.mount()`

While you can use the [mount()](/app/component-testing/vue/api#mount)
function in your tests, we recommend using [`cy.mount()`](/api/commands/mount),
which is a [custom command](/api/cypress-api/custom-commands) that is defined in
the **cypress/support/component.js** file:

```js title=cypress/support/component.js
import { mount } from 'cypress/vue'

Cypress.Commands.add('mount', mount)
```

This allows you to use `cy.mount()` in any test without having to import the
`mount()` function in each and every spec file.

By default, `cy.mount()` is a simple passthrough to `mount()`, however, you can
customize `cy.mount()` to fit your needs. For instance, if you are using plugins
or other global app-level setups in your Vue app, you can configure them here.

Below are a few examples that demonstrate using a custom mount command. These
examples can be adjusted for most other providers that you will need to support.

### Replicating Plugins

Most applications will have state management or routing. Both of these are Vue
plugins.

<Tabs>
<TabItem value="cypress/support/component.js">

```js
import { createPinia } from 'pinia' // or Vuex
import { createI18n } from 'vue-i18n'
import { mount } from 'cypress/vue'
import { h } from 'vue'

// We recommend that you pull this out
// into a constants file that you share with
// your main.js file.
const i18nOptions = {
  locale: 'en',
  messages: {
    en: {
      hello: 'hello!',
    },
    ja: {
      hello: 'こんにちは！',
    },
  },
}

Cypress.Commands.add('mount', (component, ...args) => {
  args.global = args.global || {}
  args.global.plugins = args.global.plugins || []
  args.global.plugins.push(createPinia())
  args.global.plugins.push(createI18n())

  return mount(
    () => {
      return h(VApp, {}, component)
    },
    ...args
  )
})
```

</TabItem>
<TabItem value="With JSX">

```jsx
import { createPinia } from 'pinia' // or Vuex
import { createI18n } from 'vue-i18n'
import { mount } from 'cypress/vue'

// We recommend that you pull this out
// into a constants file that you share with
// your main.js file.
const i18nOptions = {
  locale: 'en',
  messages: {
    en: {
      hello: 'hello!',
    },
    ja: {
      hello: 'こんにちは！',
    },
  },
}

Cypress.Commands.add('mount', (component, ...args) => {
  args.global = args.global || {}
  args.global.plugins = args.global.plugins || []
  args.global.plugins.push(createPinia())
  args.global.plugins.push(createI18n())

  // <component> is a built-in component that comes with Vue
  return mount(
    () => (
      <VApp>
        <component is={component} />
      </VApp>
    ),
    ...args
  )
})
```

</TabItem>
</Tabs>

### Replicating the expected Component Hierarchy

Some Vue applications, most famously Vue apps built on top of Vuetify, require
certain components to be structured in a specific hierarchy.

All Vuetify applications require that you wrap your app in a `VApp` component
when you build it. This is an implementation detail of Vuetify, but once users
try to test components that depend on Vuetify, they get Vuetify-specific
compilation errors and quickly find out that **they need to replicate that
component hierarchy any time they need to mount a component that uses a Vuetify
component**!

Custom `cy.mount` commands to the rescue! You may find the JSX syntax to be more
straightforward.

You'll also need to replicate the plugin setup steps from the Vuetify docs for
everything to compile.

<Tabs>
<TabItem value="cypress/support/component.js">

```js
import Vuetify from 'vuetify/lib'
import { VApp } from 'vuetify'
import { mount } from 'cypress/vue'
import { h } from 'vue'

// We recommend that you pull this out
// into a constants file that you share with
// your main.js file.
const vuetifyOptions = {}

Cypress.Commands.add('mount', (component, ...args) => {
  args.global = args.global || {}
  args.global.plugins = args.global.plugins || []
  args.global.plugins.push(new Vuetify(vuetifyOptions))

  return mount(
    () => {
      return h(VApp, {}, component)
    },
    ...args
  )
})
```

</TabItem>
<TabItem value="With JSX">

```jsx
import Vuetify from 'vuetify/lib'
import { VApp } from 'vuetify'
import { mount } from 'cypress/vue'

// We recommend that you pull this out
// into a constants file that you share with
// your main.js file.
const vuetifyOptions = {}

Cypress.Commands.add('mount', (component, ...args) => {
  args.global = args.global || {}
  args.global.plugins = args.global.plugins || []
  args.global.plugins.push(new Vuetify(vuetifyOptions))

  // <component> is a built-in component that comes with Vue
  return mount(
    () => (
      <VApp>
        <component is={component} />
      </VApp>
    ),
    ...args
  )
})
```

</TabItem>
</Tabs>

### Vue Router

To use Vue Router, create a command to register the plugin and pass in a custom
implementation of the router via the options param.

<Tabs>
<TabItem value="cypress/support/component.js">

```js
import { mount } from 'cypress/vue'
import { createMemoryHistory, createRouter } from 'vue-router'
import { routes } from '../../src/router'

Cypress.Commands.add('mount', (component, options = {}) => {
  // Setup options object
  options.global = options.global || {}
  options.global.plugins = options.global.plugins || []

  // create router if one is not provided
  if (!options.router) {
    options.router = createRouter({
      routes: routes,
      history: createMemoryHistory(),
    })
  }

  // Add router plugin
  options.global.plugins.push({
    install(app) {
      app.use(options.router)
    },
  })

  return mount(component, options)
})
```

</TabItem>
<TabItem value="Typings">

```ts
import { mount } from 'cypress/vue'
import { Router } from 'vue-router'

type MountParams = Parameters<typeof mount>
type OptionsParam = MountParams[1] & { router?: Router }

declare global {
  namespace Cypress {
    interface Chainable {
      /**
       * Helper mount function for Vue Components
       * @param component Vue Component or JSX Element to mount
       * @param options Options passed to Vue Test Utils
       */
      mount(component: any, options?: OptionsParam): Chainable<any>
    }
  }
}
```

</TabItem>
<TabItem value="Spec Usage">

```js
import Navigation from './Navigation.vue'
import { routes } from '../router'
import { createMemoryHistory, createRouter } from 'vue-router'

it('home link should be active when url is "/"', () => {
  // No need to pass in custom router as default url is '/'
  cy.mount(<Navigation />)

  cy.get('a').contains('Home').should('have.class', 'router-link-active')
})

it('login link should be active when url is "/login"', () => {
  // Create a new router instance for each test
  const router = createRouter({
    routes: routes,
    history: createMemoryHistory(),
  })

  // Change location to `/login`,
  // and await on the promise with cy.wrap
  cy.wrap(router.push('/login'))

  // Pass the already initialized router for use
  cy.mount(<Navigation />, { router })

  cy.get('a').contains('Login').should('have.class', 'router-link-active')
})
```

:::info

Calling `router.push()` in the router for Vue 3 is an asynchronous operation.
Use the [cy.wrap](/api/commands/wrap) command to have Cypress await the
promise's resolve before it continues with other commands:

:::

</TabItem>
</Tabs>

### Vuex

To use a component that uses [Vuex](https://vuex.vuejs.org/), create a `mount`
command that configures a Vuex store for your component.

<Tabs>
<TabItem value="cypress/support/component.js">

```js
import { mount } from 'cypress/vue'
import { getStore } from '../../src/plugins/store'

Cypress.Commands.add('mount', (component, options = {}) => {
  // Setup options object
  options.global = options.global || {}
  options.global.stubs = options.global.stubs || {}
  options.global.stubs['transition'] = false
  options.global.components = options.global.components || {}
  options.global.plugins = options.global.plugins || []

  // Use store passed in from options, or initialize a new one
  const { store = getStore(), ...mountOptions } = options

  // Add Vuex plugin
  options.global.plugins.push({
    install(app) {
      app.use(store)
    },
  })

  return mount(component, mountOptions)
})
```

:::info

The `getStore` method is a factory method that initializes Vuex and creates a
new store. It is important that the store be initialized with each new test to
ensure changes to the store don't affect other tests.

:::

</TabItem>
<TabItem value="Typings">

```ts
import { mount } from 'cypress/vue'
import { Store } from 'vuex'

type MountParams = Parameters<typeof mount>
type OptionsParam = MountParams[1]

declare global {
  namespace Cypress {
    interface Chainable {
      /**
       * Helper mount function for Vue Components
       * @param component Vue Component or JSX Element to mount
       * @param options Options passed to Vue Test Utils
       */
      mount(
        component: any,
        options?: OptionsParam & { store?: Store }
      ): Chainable<any>
    }
  }
}
```

</TabItem>
<TabItem value="Spec Usage">

```js
import { getStore } from '@/plugins/store'
import UserProfile from './UserProfile.vue'

it.only('User profile should display user name', () => {
  const user = { name: 'test person' }

  // getStore is a factory method that creates a new store
  const store = getStore()

  // mutate the store with user
  store.commit('setUser', user)

  cy.mount(UserProfile, {
    store,
  })

  cy.get('div.name').should('have.text', user.name)
})
```

</TabItem>
</Tabs>

### Global Components

If you have components that are registered globally in the main application
file, set them up in your mount command so your component will render them
properly:

```js
import { mount } from 'cypress/vue'
import Button from '../../src/components/Button.vue'

Cypress.Commands.add('mount', (component, options = {}) => {
  // Setup options object
  options.global = options.global || {}
  options.global.components = options.global.components || {}

  // Register global components
  options.global.components['Button'] = Button

  return mount(component, options)
})
```
